﻿using Authentication.Data;
using Authentication.Data.Repository;
using Authentication.Model.Interface;
using Authentication.Model.Model;
using Common.Contract;
using Common.Util;
using System;
using System.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Authentication.Provider
{
    public class AccountProvider : GenericProvider, IUserProvider
    {
        public IEnumerable<Account> RetrieveUsers(string applicationKey = null)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Account> RetrieveUsers(out int totalRecord, Guid? applicationKey = null, string groupidentifier = null, string username = null, bool? active = null, int pagesize = 0, int pageindex = 0)
        {
            var query = repository.GetQuery<Account>();
            if (applicationKey.HasValue)
            {
                var appKey = applicationKey.ToString().ToUpper();
                query = query.Where(x => x.ApplicationKey.ToUpper() == appKey);
            }

            if (!username.IsNullOrWhiteSpace())
            {
                query = query.Where(x => x.UserName.ToUpper().Contains(username.ToUpper()) ||
                                         x.AccountInfo.FirstName.ToUpper().Contains(username.ToUpper()) ||
                                         x.AccountInfo.LastName.ToUpper().Contains(username.ToUpper()));
            }

            if (!groupidentifier.IsNullOrWhiteSpace())
            {
                query = query.Where(x => x.GroupId.ToUpper() == groupidentifier.ToUpper());
            }

            if (active.HasValue)
            {
                var activeCheck = active.Value.ToBooleanString();
                query = query.Where(x => x.Active == activeCheck);
            }
            query = query.OrderBy(x => x.CreateDate) as IQueryable<Account>;
            totalRecord = query.Count();


            if (pagesize > 0 && pageindex >= 0)
            {
                query = query.Skip(pageindex * pagesize).Take(pagesize);
            }

            return query.Include(x => x.AccountInfo).Include(x => x.Group).ToList();
        }

        public Account RetrieveUser(Guid applicationKey, string userName)
        {
            var appkey = applicationKey.ToString();
            var data = repository.DbContext as AuthenticationEntities;
            var querry = from x in data.Accounts.Include(x => x.AccountInfo).Include(x => x.Group)
                         where x.ApplicationKey.ToUpper() == appkey.ToUpper() && x.UserName.ToUpper() == userName.ToUpper()
                         select x;
            return querry.FirstOrDefault();
        }

        public Account RetrieveUserByEmail(Guid applicationKey, string email)
        {
            throw new NotImplementedException();
        }


        public bool AddUser(Account record)
        {
            try
            {
                repository.Add<Account>(record);
                repository.UnitOfWork.SaveChanges();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool UpdateUser(Account record)
        {
            var updateRecord = RetrieveUser(record.GuidKey, record.UserName);
            if (updateRecord == null) return false;

            updateRecord.Active = record.IsActive.ToBooleanString();
            if (!record.Password.IsNullOrWhiteSpace()) updateRecord.Password = record.Password;
            updateRecord.UpdateDate = DateTime.Now;
            updateRecord.GroupId = record.GroupId;
            updateRecord.Active = record.Active;

            if (record.AccountInfo != null)
            {
                if (updateRecord.AccountInfo == null)
                {
                    updateRecord.AccountInfo = new AccountInfo { ApplicationKey = record.ApplicationKey, UserName = record.UserName };
                }
                updateRecord.AccountInfo.Email = record.AccountInfo.Email;
                updateRecord.AccountInfo.Address = record.AccountInfo.Address;
                updateRecord.AccountInfo.Birthday = record.AccountInfo.Birthday;
                updateRecord.AccountInfo.CanContactByEmail = record.AccountInfo.CanContactByEmail;
                updateRecord.AccountInfo.FirstName = record.AccountInfo.FirstName;
                updateRecord.AccountInfo.LastName = record.AccountInfo.LastName;
                updateRecord.AccountInfo.Gender = record.AccountInfo.Gender;
                updateRecord.AccountInfo.IsApproved = record.AccountInfo.IsApproved ?? BooleanString.TRUE;
                updateRecord.AccountInfo.IsAnonymous = record.AccountInfo.IsAnonymous ?? BooleanString.FALSE;
                updateRecord.AccountInfo.IsDeleted = record.AccountInfo.IsDeleted ?? BooleanString.FALSE;
                updateRecord.AccountInfo.IsLockedout = record.AccountInfo.IsLockedout ?? BooleanString.TRUE;
                updateRecord.AccountInfo.IsSuspended = record.AccountInfo.IsSuspended;
                updateRecord.AccountInfo.LastPasswordChangedDate = record.AccountInfo.LastPasswordChangedDate;
                updateRecord.AccountInfo.MobileAlias = record.AccountInfo.MobileAlias;
                updateRecord.AccountInfo.MobilePin = record.AccountInfo.MobilePin;
            }

            repository.UnitOfWork.SaveChanges();
            return true;
        }

        public bool UpdateUserInfo(AccountInfo record)
        {
            var updateRecord = RetrieveUser(record.GuidKey, record.UserName);
            if (updateRecord.AccountInfo != null)
            {
                updateRecord.AccountInfo.Email = record.Email;
                updateRecord.AccountInfo.Address = record.Address;
                updateRecord.AccountInfo.Birthday = record.Birthday;
                updateRecord.AccountInfo.CanContactByEmail = record.CanContactByEmail;
                updateRecord.AccountInfo.FirstName = record.FirstName;
                updateRecord.AccountInfo.LastName = record.LastName;
                updateRecord.AccountInfo.Gender = record.Gender;
                updateRecord.AccountInfo.IsApproved = record.IsApproved ?? BooleanString.TRUE;
                updateRecord.AccountInfo.IsAnonymous = record.IsAnonymous ?? BooleanString.FALSE;
                updateRecord.AccountInfo.IsDeleted = record.IsDeleted ?? BooleanString.FALSE;
                updateRecord.AccountInfo.IsLockedout = record.IsLockedout ?? BooleanString.TRUE;
                updateRecord.AccountInfo.IsSuspended = record.IsSuspended;
                updateRecord.AccountInfo.LastPasswordChangedDate = record.LastPasswordChangedDate;
                updateRecord.AccountInfo.MobileAlias = record.MobileAlias;
                updateRecord.AccountInfo.MobilePin = record.MobilePin;
                repository.UnitOfWork.SaveChanges();
                return true;
            }
            else
            {
                return false;
            }
        }

        public ResponseRecord CheckRulePassword(string username, string password)
        {
            ResponseRecord result = ResponseRecord.Default;
            var passwordAudit = new List<AccountPasswordAudit>();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
            {
                result.ErrorMessage = "Can not use blank user name or password.";
                result.IsSuccess = false;
                return result;
            }

            // Different from Payroll’s account
            if (username.ToUpper() == password.ToUpper())
            {
                result.ErrorMessage = "Password can not be the same user account.";
                result.IsSuccess = false;
                return result;
            }

            for (int id = 0; id < password.Length - 2; id++)
            {
                //  Same characters/numbers 3 times in a row are not allowed 
                if (char.IsDigit(password[id]) && password[id] == password[id + 1] && password[id] == password[id + 2])
                {
                    result.ErrorMessage = "Can not be allowed same number characters  3 times in a password row.";
                    result.IsSuccess = false;
                    return result;
                }

                //  Continuation characters/numbers 3 times in a row are not  allowed
                if (char.IsDigit(password[id]) && password[id] + 1 == password[id + 1] && password[id + 1] + 1 == password[id + 2])
                {
                    result.ErrorMessage = "Can not be allowed continuation number characters 3 times in a password row.";
                    result.IsSuccess = false;
                    return result;
                }
            }

            var lastAudits = repository.Find<AccountPasswordAudit>(x => x.UserName.ToUpper() == username.ToUpper()).OrderByDescending(x => x.CreationDate);
            var lastAudit = lastAudits.FirstOrDefault();
            if (lastAudit != null)
            {
                // The new password should not include the same digit (70%) comparing with the current password.
                var rateDiff = StringCompare(lastAudit.Password, password);
                if (rateDiff >= 70)
                {
                    result.ErrorMessage = "The new password should not include the same digit (70%) comparing with the current password.";
                    result.IsSuccess = false;
                    return result;
                }
            }

            if (lastAudits != null)
            {
                if ((lastAudits.Count() > 0 && lastAudits.ElementAt(0).Password == password)
                    || (lastAudits.Count() > 1 && lastAudits.ElementAt(1).Password == password)
                    || (lastAudits.Count() > 2 && lastAudits.ElementAt(2).Password == password))
                {
                    result.ErrorMessage = "Can not be allowed same 3 nearest times.";
                    result.IsSuccess = false;
                    return result;
                }
            }
            return result;
        }

        #region Helper method
        private double StringCompare(string a, string b)
        {
            if (a == b) //Same string, no iteration needed.
                return 100;
            if ((a.Length == 0) || (b.Length == 0)) //One is empty, second is not
            {
                return 0;
            }
            double maxLen = a.Length > b.Length ? a.Length : b.Length;
            int minLen = a.Length < b.Length ? a.Length : b.Length;
            int sameCharAtIndex = 0;
            for (int i = 0; i < minLen; i++) //Compare char by char
            {
                if (a[i] == b[i])
                {
                    sameCharAtIndex++;
                }
            }
            return sameCharAtIndex / maxLen * 100;
        }
        #endregion


        public long GetMaxAccountLogonAuditID(Guid applicationKey, string username)
        {
            var appkey = applicationKey.ToString();
            var result = repository.Find<AccountPasswordAudit>(x => x.ApplicationKey.ToUpper() == appkey.ToUpper() && x.UserName.ToUpper() == username.ToUpper()).OrderByDescending(y => y.Id).FirstOrDefault();
            if (result == null) return 0;
            return result.Id;
        }

        public void AddAccountLogonAudit(AccountLogonAudit userLogonAudit)
        {
            repository.Add<AccountLogonAudit>(userLogonAudit);
            repository.UnitOfWork.SaveChanges();
        }
    }
}
